﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Text.RegularExpressions;
using IGolovkine.ShoutCastRipper.Filters;
using IGolovkine.Core.Diagnostics;
using IGolovkine.Core;

namespace IGolovkine.ShoutCastRipper
{
    public class StationRipper : IStationRipper
    {
        private readonly IStationClient _client;
        private readonly ILogWriter _logWriter;
        private readonly IContentRepository _repository;
        private readonly List<IContentTitleFilter> _titleFilters;
        private readonly IContentTitleProbingPolicy _probingPolicy;
        private readonly IContentLengthPolicy _lengthPolicy;

        //public event EventHandler<EventArgs> Connected;
        //public event EventHandler<EventArgs> Disconnected;
        //public event EventHandler<EventArgs> SkippingTitle;
        //public event EventHandler<EventArgs> RecordingTitle;

        private readonly string _name;
        private volatile bool _started;

        private bool _stopAfterCurrentTrack;
        private string _contentTitleToSkip;
        private int _uninterruptedTitleCount;


        public StationRipper(string name,
            TypeFactory<IStationClient> stationFactory,
            IContentRepository contentRepository,
            IContentTitleProbingPolicy probingPolicy,
            IContentLengthPolicy lengthPolicy,
            ILogWriter logWriter)
        {
            Debug.NotNullOrEmpty(() => name);
            Debug.NotDefault(() => stationFactory);
            Debug.NotDefault(() => contentRepository);
            Debug.NotDefault(() => probingPolicy);
            Debug.NotDefault(() => lengthPolicy);
            Debug.NotDefault(() => logWriter);

            _name = name;
            _logWriter = logWriter;
            _lengthPolicy = lengthPolicy;

            _repository = contentRepository;
            
            _client = stationFactory();
            _probingPolicy = probingPolicy;

            _probingPolicy.ProbeContentTitle += new EventHandler<EventArgs>(ProbeContentTitle);
            
            _client.Disconnected += new EventHandler<DisconnectedEventArgs>(ListenerDisconnected);
            _client.ContentStart += new EventHandler<ContentStartEventArgs>(ListenerContentStart);
            _client.ContentEnd += new EventHandler<ContentEndEventArgs>(ListenerContentEnd);
            _client.ContentReceived += new EventHandler<ContentReceivedEventArgs>(ListenerStreamDataReceived);

            _titleFilters = new List<IContentTitleFilter>();
        }

        public IList<IContentTitleFilter> TitleFilters
        {
            get { return _titleFilters; }
        }

        public bool IsConnected
        {
            get { return this._client.IsConnected || this.SkippingCurrentTitle; }
        }

        public void Dispose()
        {
            _client.Disconnected -= new EventHandler<DisconnectedEventArgs>(ListenerDisconnected);
            _client.ContentStart -= new EventHandler<ContentStartEventArgs>(ListenerContentStart);
            _client.ContentEnd -= new EventHandler<ContentEndEventArgs>(ListenerContentEnd);
            _client.ContentReceived -= new EventHandler<ContentReceivedEventArgs>(ListenerStreamDataReceived);
            _client.Dispose();

            _probingPolicy.ProbeContentTitle -= new EventHandler<EventArgs>(ProbeContentTitle);
            _probingPolicy.Dispose();

            this.Stop();
        }

        private void ListenerContentStart(object sender, ContentStartEventArgs e)
        {
            if (_contentTitleToSkip == e.ContentTitle)
            {
                _client.Disconnect();
            }
            else
            {
                _contentTitleToSkip = null;
                _logWriter.WriteLine();
                _logWriter.WriteLine("Current content title is '{0}'", e.ContentTitle);
                _uninterruptedTitleCount++;
                if (!this.ProcessStreamTitleFilters(e.ContentTitle))
                {
                    this.SkipCurrentTitle();
                }
                else
                {
                    _repository.BeginStoreContent(e.ContentTitle);
                    _logWriter.WriteLine("Recording '{0}'", e.ContentTitle);
                }
            }
        }

        private void ListenerContentEnd(object sender, ContentEndEventArgs e)
        {
            if (_lengthPolicy.WithinMinimumLength(e.ContentLength))
            {
                _repository.EndStoreContent(_uninterruptedTitleCount > 1);
            }
            else
            {
                if (_repository.SavingContent)
                {
                    _logWriter.WriteLine("{0} doesn't satisfy minimum length requirement, deleting...", e.ContentTitle);
                    _repository.CancelStoreContent();
                }
            }

            if (_stopAfterCurrentTrack)
            {
                this.Stop();
                _stopAfterCurrentTrack = false;
            }
        }
        
        private void ListenerDisconnected(object sender, DisconnectedEventArgs e)
        {
            if (this.SkippingCurrentTitle && e.Exception == null)
            {
                _logWriter.WriteLine("Skipping '{0}'", e.ContentTitle);
            }
            else
            {
                _uninterruptedTitleCount = 0;
                if (e.Exception != null)
                {
                    _logWriter.WriteException("Disconnected", e.Exception);
                }
                else
                {
                    _logWriter.WriteLine("Disconnected");
                }
            }

            if (_started)
            {
                _probingPolicy.Schedule(e.Exception);
            }
        }

        private bool ProcessStreamTitleFilters(string streamTitle)
        {
            foreach (IContentTitleFilter filter in _titleFilters)
            {
                ContentTitleFilterResult result = filter.Process(streamTitle);
                if (result.Rejected)
                {
                    _logWriter.WriteLine("'{0}' was blocked by '{1}': {2}, skipping", streamTitle, filter.Name, result.RejectionReason);
                    return false;
                }
            }
            return true;
        }

        private void ListenerStreamDataReceived(object sender, ContentReceivedEventArgs e)
        {
            if (!_lengthPolicy.WithinMaximumLength(e.ContentLength))
            {
                _logWriter.WriteLine("{0} exceeds maximum length, skipping...", e.ContentTitle);
                this.SkipCurrentTitle();
            }
            else
            {
                _repository.AppendContent(e.Data);
            }
        }

        public void Start()
        {
            if (_started == false)
            {
                _started = true;
                _logWriter.WriteLine("{0} for '{1}' started!", this.GetType().Name, _name);
                this.TryConnect();
            }
            else
            {
                throw new InvalidOperationException(string.Format("{0} is already started", this.GetType().Name));
            }
        }

        private void ProbeContentTitle(object sender, EventArgs e)
        {
            this.TryConnect();
        }

        private bool TryConnect()
        {
            try
            {
                _client.Connect();
                return true;
            }
            catch (Exception ex)
            {
                _logWriter.WriteException("Error connecting", ex);
                _probingPolicy.Schedule(ex);
                return false;
            }
        }

        public bool IsStarted
        {
            get { return _started; }
        }

        public void Stop()
        {
            if (_started)
            {
                if (_client.IsConnected)
                {
                    _client.Disconnect();
                }
                _started = false;
            }
            else
            {
                throw new InvalidOperationException(string.Format("{0} is already stopped", this.GetType().Name));
            }

            _logWriter.WriteLine("{0} stopped!", this.GetType().Name);
        }

        public void StopAfterCurrentTitle()
        {
            _stopAfterCurrentTrack = true;
        }

        public bool SkippingCurrentTitle
        {
            get { return _contentTitleToSkip != null; }
        }

        public void SkipCurrentTitle()
        {
            _contentTitleToSkip = _client.CurrentTitle;
            _client.Disconnect();
        }

        //protected virtual void OnConnected(StationRipperConnectedEventArgs args) 
        //{
        //    if( this.Connected != null ) 
        //    {
        //        this.Connected(this, args);
        //    }
        //}

        //protected virtual void OnDisconnected(StationRipperDisconnectedEventArgs args)
        //{
        //}
    }
}
